package com.ehome.manage.module.utils.process.engine;

import com.ehome.manage.module.utils.process.context.BaseProcessContext;
import com.ehome.manage.module.utils.process.node.CommonNodeName;
import com.ehome.manage.module.utils.process.node.NodeResult;
import com.ehome.manage.module.utils.process.node.ProcessNode;
import com.ehome.manage.module.utils.process.process.ProcessDefinition;
import com.google.common.base.Stopwatch;
import lombok.extern.slf4j.Slf4j;

import javax.annotation.ManagedBean;

/**
 * 流程引擎实现
 *
 * @author xiaxinye
 */
@Slf4j
@ManagedBean
public class ProcessEngineImpl implements ProcessEngine {

    /**
     * 从第一个节点启动流程，“第一个节点”的名称定义于NodeName接口
     *
     * @param process 流程定义
     * @param context 流程上下文
     * @param <C>     流程上下文类型
     */
    @Override
    public <C extends BaseProcessContext> void execute(ProcessDefinition<C> process, C context) {
        execute(process, context, CommonNodeName.FIRST);
    }

    /**
     * 从指定节点启动流程
     *
     * @param process 流程定义
     * @param context 流程上下文
     * @param <C>     流程上下文类型
     */
    @Override
    public <C extends BaseProcessContext> void execute(ProcessDefinition<C> process, C context, String nodeName) {
        // 流程计时开始
        Stopwatch stopwatch = Stopwatch.createStarted();
        if (process.nodeLogEnabled()) {
            log.info("准备调用[{}]流程", process.getName());
        }
        try {
            // 查找第一个节点并开始流程处理
            findAndProcessNodes(process, nodeName, context);
        } finally {
            // 查找流程是否声明了FINAL节点
            ProcessNode<C> finalNode = process.getNodeByName(CommonNodeName.FINAL);
            // 若声明了则执行FINAL节点
            if (finalNode != null) {
                Stopwatch finalNodeWatch = Stopwatch.createStarted();
                try {
                    finalNode.execute(context);
                } catch (Throwable e) {
                    log.error("[{}]流程在[FINAL]节点发生异常", process.getName(), e);
                } finally {
                    if (process.nodeLogEnabled()) {
                        log.info("[{}]流程FINAL节点耗时[{}]", process.getName(), finalNodeWatch.stop());
                    }
                }
            }
            if (process.nodeLogEnabled()) {
                log.info("[{}]流程总耗时[{}]", process.getName(), stopwatch.stop());
            }
        }
    }

    @Override
    public <C extends BaseProcessContext> void execute(ProcessDefinition<C> process) {
        execute(process, process.createContext());
    }

    /**
     * 根据名称查找指定流程的节点并执行
     *
     * @param process  流程定义
     * @param nodeName 节点名称
     * @param context  流程上下文
     * @param <C>      流程上下文类型
     */
    private <C extends BaseProcessContext> void findAndProcessNodes(ProcessDefinition<C> process, String nodeName, C context) {
        // 根据名字找到流程节点
        ProcessNode<C> node = process.getNodeByName(nodeName);
        // 节点校验
        if (node == null) {
            throw new RuntimeException("无法根据名称[" + nodeName + "]找到流程节点");
        }
        // 开始节点计时
        Stopwatch stopWatch = Stopwatch.createStarted();
        // 节点处理结果
        NodeResult result;
        try {
            // 执行节点操作
            result = node.execute(context);
        } catch (Throwable e) {
            log.error("[{}]流程在[{}]节点发生异常，流程结束", process.getName(), nodeName);
            throw e;
        } finally {
            if (process.nodeLogEnabled()) {
                log.info("[{}]流程[{}]节点耗时[{}]", process.getName(), nodeName, stopWatch.stop());
            }
        }
        if (result != null && !result.isFinish()) {
            // 若流程未结束则前往下一个节点继续执行
            findAndProcessNodes(process, result.getNextNodeName(), context);
        }
    }

}
